<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Gdata
 * @subpackage Gdata
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: HttpClient.php 18951 2009-11-12 16:26:19Z alexander $
 */

/**
 * Zend_Http_Client
 */
require_once 'Zend/Http/Client.php';

/**
 * Gdata Http Client object.
 *
 * Class to extend the generic Zend Http Client with the ability to perform
 * secure AuthSub requests
 *
 * @category   Zend
 * @package    Zend_Gdata
 * @subpackage Gdata
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Gdata_HttpClient extends Zend_Http_Client
{

	/**
	 * OpenSSL private key resource id
	 * This key is used for AuthSub authentication.  If this value is set,
	 * it is assuemd that secure AuthSub is desired.
	 *
	 * @var resource
	 */
	private $_authSubPrivateKeyId = null;

	/**
	 * Token for AuthSub authentication.
	 * If this token is set, AuthSub authentication is used.
	 *
	 * @var string
	 */
	private $_authSubToken = null;

	/**
	 * Token for ClientLogin authentication.
	 * If only this token is set, ClientLogin authentication is used.
	 *
	 * @var string
	 */
	private $_clientLoginToken = null;

	/**
	 * Token for ClientLogin authentication.
	 * If this token is set, and the AuthSub key is not set,
	 * ClientLogin authentication is used
	 *
	 * @var string
	 */
	private $_clientLoginKey = null;

	/**
	 * True if this request is being made with data supplied by
	 * a stream object instead of a raw encoded string.
	 *
	 * @var bool
	 */
	protected $_streamingRequest = null;

	/**
	 * Sets the PEM formatted private key, as read from a file.
	 *
	 * This method reads the file and then calls setAuthSubPrivateKey()
	 * with the file contents.
	 *
	 * @param string $file The location of the file containing the PEM key
	 * @param string $passphrase The optional private key passphrase
	 * @param bool $useIncludePath Whether to search the include_path
	 *                             for the file
	 * @return void
	 */
	public function setAuthSubPrivateKeyFile($file, $passphrase = null,
	$useIncludePath = false) {
		$fp = fopen($file, "r", $useIncludePath);
		$key = '';
		while (!feof($fp)) {
			$key .= fread($fp, 8192);
		}
		$this->setAuthSubPrivateKey($key, $passphrase);
		fclose($fp);
	}

	/**
	 * Sets the PEM formatted private key to be used for secure AuthSub auth.
	 *
	 * In order to call this method, openssl must be enabled in your PHP
	 * installation.  Otherwise, a Zend_Gdata_App_InvalidArgumentException
	 * will be thrown.
	 *
	 * @param string $key The private key
	 * @param string $passphrase The optional private key passphrase
	 * @throws Zend_Gdata_App_InvalidArgumentException
	 * @return Zend_Gdata_HttpClient Provides a fluent interface
	 */
	public function setAuthSubPrivateKey($key, $passphrase = null) {
		if ($key != null && !function_exists('openssl_pkey_get_private')) {
			require_once 'Zend/Gdata/App/InvalidArgumentException.php';
			throw new Zend_Gdata_App_InvalidArgumentException(
                    'You cannot enable secure AuthSub if the openssl module ' .
                    'is not enabled in your PHP installation.');
		}
		$this->_authSubPrivateKeyId = openssl_pkey_get_private(
		$key, $passphrase);
		return $this;
	}

	/**
	 * Gets the openssl private key id
	 *
	 * @return string The private key
	 */
	public function getAuthSubPrivateKeyId() {
		return $this->_authSubPrivateKeyId;
	}

	/**
	 * Gets the AuthSub token used for authentication
	 *
	 * @return string The token
	 */
	public function getAuthSubToken() {
		return $this->_authSubToken;
	}

	/**
	 * Sets the AuthSub token used for authentication
	 *
	 * @param string $token The token
	 * @return Zend_Gdata_HttpClient Provides a fluent interface
	 */
	public function setAuthSubToken($token) {
		$this->_authSubToken = $token;
		return $this;
	}

	/**
	 * Gets the ClientLogin token used for authentication
	 *
	 * @return string The token
	 */
	public function getClientLoginToken() {
		return $this->_clientLoginToken;
	}

	/**
	 * Sets the ClientLogin token used for authentication
	 *
	 * @param string $token The token
	 * @return Zend_Gdata_HttpClient Provides a fluent interface
	 */
	public function setClientLoginToken($token) {
		$this->_clientLoginToken = $token;
		return $this;
	}

	/**
	 * Filters the HTTP requests being sent to add the Authorization header.
	 *
	 * If both AuthSub and ClientLogin tokens are set,
	 * AuthSub takes precedence.  If an AuthSub key is set, then
	 * secure AuthSub authentication is used, and the request is signed.
	 * Requests must be signed only with the private key corresponding to the
	 * public key registered with Google.  If an AuthSub key is set, but
	 * openssl support is not enabled in the PHP installation, an exception is
	 * thrown.
	 *
	 * @param string $method The HTTP method
	 * @param string $url The URL
	 * @param array $headers An associate array of headers to be
	 *                       sent with the request or null
	 * @param string $body The body of the request or null
	 * @param string $contentType The MIME content type of the body or null
	 * @throws Zend_Gdata_App_Exception if there was a signing failure
	 * @return array The processed values in an associative array,
	 *               using the same names as the params
	 */
	public function filterHttpRequest($method, $url, $headers = array(), $body = null, $contentType = null) {
		if ($this->getAuthSubToken() != null) {
			// AuthSub authentication
			if ($this->getAuthSubPrivateKeyId() != null) {
				// secure AuthSub
				$time = time();
				$nonce = mt_rand(0, 999999999);
				$dataToSign = $method . ' ' . $url . ' ' . $time . ' ' . $nonce;

				// compute signature
				$pKeyId = $this->getAuthSubPrivateKeyId();
				$signSuccess = openssl_sign($dataToSign, $signature, $pKeyId,
				OPENSSL_ALGO_SHA1);
				if (!$signSuccess) {
					require_once 'Zend/Gdata/App/Exception.php';
					throw new Zend_Gdata_App_Exception(
                            'openssl_signing failure - returned false');
				}
				// encode signature
				$encodedSignature = base64_encode($signature);

				// final header
				$headers['authorization'] = 'AuthSub token="' . $this->getAuthSubToken() . '" ' .
                                            'data="' . $dataToSign . '" ' .
                                            'sig="' . $encodedSignature . '" ' .
                                            'sigalg="rsa-sha1"';
			} else {
				// AuthSub without secure tokens
				$headers['authorization'] = 'AuthSub token="' . $this->getAuthSubToken() . '"';
			}
		} elseif ($this->getClientLoginToken() != null) {
			$headers['authorization'] = 'GoogleLogin auth=' . $this->getClientLoginToken();
		}
		return array('method' => $method, 'url' => $url, 'body' => $body, 'headers' => $headers, 'contentType' => $contentType);
	}

	/**
	 * Method for filtering the HTTP response, though no filtering is
	 * currently done.
	 *
	 * @param Zend_Http_Response $response The response object to filter
	 * @return Zend_Http_Response The filterd response object
	 */
	public function filterHttpResponse($response) {
		return $response;
	}

	/**
	 * Return the current connection adapter
	 *
	 * @return Zend_Http_Client_Adapter_Interface|string $adapter
	 */
	public function getAdapter()
	{
		return $this->adapter;
	}

	/**
	 * Load the connection adapter
	 *
	 * @param Zend_Http_Client_Adapter_Interface $adapter
	 * @return void
	 */
	public function setAdapter($adapter)
	{
		if ($adapter == null) {
			$this->adapter = $adapter;
		} else {
			parent::setAdapter($adapter);
		}
	}

	/**
	 * Set the streamingRequest variable which controls whether we are
	 * sending the raw (already encoded) POST data from a stream source.
	 *
	 * @param boolean $value The value to set.
	 * @return void
	 */
	public function setStreamingRequest($value)
	{
		$this->_streamingRequest = $value;
	}

	/**
	 * Check whether the client is set to perform streaming requests.
	 *
	 * @return boolean True if yes, false otherwise.
	 */
	public function getStreamingRequest()
	{
		if ($this->_streamingRequest()) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Prepare the request body (for POST and PUT requests)
	 *
	 * @return string
	 * @throws Zend_Http_Client_Exception
	 */
	protected function _prepareBody()
	{
		if($this->_streamingRequest) {
			$this->setHeaders(self::CONTENT_LENGTH,
			$this->raw_post_data->getTotalSize());
			return $this->raw_post_data;
		}
		else {
			return parent::_prepareBody();
		}
	}

	/**
	 * Clear all custom parameters we set.
	 *
	 * @return Zend_Http_Client
	 */
	public function resetParameters()
	{
		$this->_streamingRequest = false;

		return parent::resetParameters();
	}

	/**
	 * Set the raw (already encoded) POST data from a stream source.
	 *
	 * This is used to support POSTing from open file handles without
	 * caching the entire body into memory. It is a wrapper around
	 * Zend_Http_Client::setRawData().
	 *
	 * @param string $data The request data
	 * @param string $enctype The encoding type
	 * @return Zend_Http_Client
	 */
	public function setRawDataStream($data, $enctype = null)
	{
		$this->_streamingRequest = true;
		return $this->setRawData($data, $enctype);
	}

}
